﻿using System;
using System.Collections.Generic;

namespace MLSharp.Filters
{
	/// <summary>
	/// Performs Z standardization.
	/// </summary>
	/// <remarks>
	/// The basic formula applied to standardized values is:
	/// 
	/// Si = (Xi - mean)/stdev(X)
	/// </remarks>
	public class ZStandardizeFilter : StandardizeFilter
	{
		#region Public Constructors

		/// <summary>
		/// Calculates how to perform standardization 
		/// based on the values in the data set.
		/// </summary>
		/// <param name="dataSet"></param>
		public ZStandardizeFilter(IDataSet dataSet) : base(dataSet)
		{
			
		}

		#endregion

		#region Protected Overrides

		/// <summary>
		///	Calculates a standardization step for a continuous-valued variable.
		/// </summary>
		/// <param name="index"></param>
		/// <param name="dataSet"></param>
		/// <returns></returns>
		protected override StandardizationStepBase GetContinuousStep(int index, IDataSet dataSet)
		{
			double total = 0;
			List<double> values = new List<double>();

			for (int i=0; i < dataSet.Instances.Count; i++)
			{
				//skip missing values.
				if (dataSet.Instances[i].IsValueMissing(index))
				{
					continue;
				}

				total += dataSet.Instances[i].Values[index];
				values.Add(dataSet.Instances[i].Values[index]);
			}

			double average = total/values.Count;

			//Now calculate the standard deviation
			double sum = 0;

			foreach (double value in values)
			{
				sum += (value - average)*(value - average);
			}

			double stdev = Math.Sqrt(sum/values.Count);

			return new ZCountinousStandardizationStep(average, stdev);
		}

		#endregion

		#region Nested Classes

		/// <summary>
		/// A step that applies Z-standardization to a continous attribute.
		/// </summary>
		protected class ZCountinousStandardizationStep : StandardizationStepBase
		{
			#region Private Fields

			/// <summary>
			/// The average value for the variable.
			/// </summary>
			private readonly double mAverage;

			/// <summary>
			/// The stdev for the variable.
			/// </summary>
			private readonly double mStdev;

			#endregion

			#region Public Constructors

			/// <summary>
			/// Creates the standardization step.
			/// </summary>
			/// <param name="average"></param>
			/// <param name="stdev"></param>
			public ZCountinousStandardizationStep(double average, double stdev)
			{
				mAverage = average;
				mStdev = stdev;
			}

			#endregion

			#region Public Methods

			/// <summary>
			/// Applies the standardization to the specified value.
			/// </summary>
			/// <param name="value"></param>
			/// <returns></returns>
			public override double[] Apply(double value)
			{
				if (double.IsNaN(value))
				{
					return new [] { double.NaN };
				}

				value = (value - mAverage)/mStdev;

				return new [] {value};
			}

			#endregion
		}

		#endregion
	}
}
